/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                        Intel License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2008, Nils Hasler, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of Intel Corporation may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

// Author: Nils Hasler <hasler@mpi-inf.mpg.de>
//
//         Max-Planck-Institut Informatik

//
// capture video from a sequence of images
// the filename when opening can either be a printf pattern such as
// video%04d.png or the first frame of the sequence i.e. video0001.png
//

#include "precomp.hpp"
#include <sys/stat.h>

#ifdef NDEBUG
#define CV_WARN(message)
#else
#define CV_WARN(message) fprintf(stderr, "warning: %s (%s:%d)\n", message, __FILE__, __LINE__)
#endif

#ifndef _MAX_PATH
#define _MAX_PATH 1024
#endif

class CvCapture_Images : public CvCapture {
public:
	CvCapture_Images() {
		filename = 0;
		currentframe = firstframe = 0;
		length = 0;
		frame = 0;
	}

	virtual ~CvCapture_Images() {
		close();
	}

	virtual bool open(const char* _filename);
	virtual void close();
	virtual double getProperty(int);
	virtual bool setProperty(int, double);
	virtual bool grabFrame();
	virtual IplImage* retrieveFrame(int);

protected:
	char*  filename; // actually a printf-pattern
	unsigned currentframe;
	unsigned firstframe; // number of first frame
	unsigned length; // length of sequence

	IplImage* frame;
};


void CvCapture_Images::close() {
	if ( filename ) {
		free(filename);
		filename = 0;
	}
	currentframe = firstframe = 0;
	length = 0;
	cvReleaseImage( &frame );
}


bool CvCapture_Images::grabFrame() {
	char str[_MAX_PATH];
	sprintf(str, filename, firstframe + currentframe);

	cvReleaseImage(&frame);
	frame = cvLoadImage(str, CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);
	if ( frame ) {
		currentframe++;
	}

	return frame != 0;
}

IplImage* CvCapture_Images::retrieveFrame(int) {
	return frame;
}

double CvCapture_Images::getProperty(int id) {
	switch (id) {
	case CV_CAP_PROP_POS_MSEC:
		CV_WARN("collections of images don't have framerates\n");
		return 0;
	case CV_CAP_PROP_POS_FRAMES:
		return currentframe;
	case CV_CAP_PROP_POS_AVI_RATIO:
		return (double)currentframe / (double)(length - 1);
	case CV_CAP_PROP_FRAME_WIDTH:
		return frame ? frame->width : 0;
	case CV_CAP_PROP_FRAME_HEIGHT:
		return frame ? frame->height : 0;
	case CV_CAP_PROP_FPS:
		CV_WARN("collections of images don't have framerates\n");
		return 1;
	case CV_CAP_PROP_FOURCC:
		CV_WARN("collections of images don't have 4-character codes\n");
		return 0;
	}
	return 0;
}

bool CvCapture_Images::setProperty(int id, double value) {
	switch (id) {
	case CV_CAP_PROP_POS_MSEC:
	case CV_CAP_PROP_POS_FRAMES:
		if (value < 0) {
			CV_WARN("seeking to negative positions does not work - clamping\n");
			value = 0;
		}
		if (value >= length) {
			CV_WARN("seeking beyond end of sequence - clamping\n");
			value = length - 1;
		}
		currentframe = cvRound(value);
		return true;
	case CV_CAP_PROP_POS_AVI_RATIO:
		if (value > 1) {
			CV_WARN("seeking beyond end of sequence - clamping\n");
			value = 1;
		} else if (value < 0) {
			CV_WARN("seeking to negative positions does not work - clamping\n");
			value = 0;
		}
		currentframe = cvRound((length - 1) * value);
		return true;
	}
	CV_WARN("unknown/unhandled property\n");
	return false;
}

static char* icvExtractPattern(const char* filename, unsigned* offset) {
	char* name = (char*)filename;

	if ( !filename ) {
		return 0;
	}

	// check whether this is a valid image sequence filename
	char* at = strchr(name, '%');
	if (at) {
		int dummy;
		if (sscanf(at + 1, "%ud", &dummy) != 1) {
			return 0;
		}
		name = strdup(filename);
	} else { // no pattern filename was given - extract the pattern
		for (at = name; *at && !isdigit(*at); at++)
			;

		if (!at) {
			return 0;
		}

		sscanf(at, "%u", offset);

		int size = (int)strlen(filename) + 20;
		name = (char*)malloc(size);
		strncpy(name, filename, at - filename);
		name[at - filename] = 0;

		strcat(name, "%0");

		int i;
		char* extension;
		for (i = 0, extension = at; isdigit(at[i]); i++, extension++)
			;
		char places[10];
		sprintf(places, "%dd", i);

		strcat(name, places);
		strcat(name, extension);
	}

	return name;
}


bool CvCapture_Images::open(const char* _filename) {
	unsigned offset = 0;
	close();

	filename = icvExtractPattern(_filename, &offset);
	if (!filename) {
		return false;
	}

	// determine the length of the sequence
	length = 0;
	char str[_MAX_PATH];
	for (;;) {
		sprintf(str, filename, offset + length);
		struct stat s;
		if (stat(str, &s)) {
			if (length == 0 && offset == 0) { // allow starting with 0 or 1
				offset++;
				continue;
			}
		}

		if (!cvHaveImageReader(str)) {
			break;
		}

		length++;
	}

	if (length == 0) {
		close();
		return false;
	}

	firstframe = offset;
	return true;
}


CvCapture* cvCreateFileCapture_Images(const char* filename) {
	CvCapture_Images* capture = new CvCapture_Images;

	if ( capture->open(filename) ) {
		return capture;
	}

	delete capture;
	return 0;
}

//
//
// image sequence writer
//
//
class CvVideoWriter_Images : public CvVideoWriter {
public:
	CvVideoWriter_Images() {
		filename = 0;
		currentframe = 0;
	}
	virtual ~CvVideoWriter_Images() { close(); }

	virtual bool open( const char* _filename );
	virtual void close();
	virtual bool writeFrame( const IplImage* );

protected:
	char* filename;
	unsigned currentframe;
};

bool CvVideoWriter_Images::writeFrame( const IplImage* image ) {
	char str[_MAX_PATH];
	sprintf(str, filename, currentframe);
	int ret = cvSaveImage(str, image);

	currentframe++;

	return ret > 0;
}

void CvVideoWriter_Images::close() {
	if ( filename ) {
		free( filename );
		filename = 0;
	}
	currentframe = 0;
}


bool CvVideoWriter_Images::open( const char* _filename ) {
	unsigned offset = 0;

	close();

	filename = icvExtractPattern(_filename, &offset);
	if (!filename) {
		return false;
	}

	char str[_MAX_PATH];
	sprintf(str, filename, 0);
	if (!cvHaveImageWriter(str)) {
		close();
		return false;
	}

	currentframe = offset;
	return true;
}


CvVideoWriter* cvCreateVideoWriter_Images( const char* filename ) {
	CvVideoWriter_Images* writer = new CvVideoWriter_Images;

	if ( writer->open( filename )) {
		return writer;
	}

	delete writer;
	return 0;
}
